package com.tanx.entity.system;

import com.tanx.annotation.Log;
import com.tanx.entity.User;
import com.tanx.entity.UserAccount;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by 唐旭 on 2015/7/8.
 */
@Component
@Aspect
public class UserOperationLogAspect {

    //${username}用户登录${username},${UserAccount.username}
    //${(.*)}
    private static final String EXPRESSION_PREFIX = "\\$\\{";
    private static final String EXPRESSION_KEY = "(.*?)";
    private static final String EXPRESSION_SUFFIX = "\\}";
    private static final String EXPRESSION_FUll = EXPRESSION_PREFIX + EXPRESSION_KEY + EXPRESSION_SUFFIX;

    @AfterReturning(value = "@annotation(com.tanx.annotation.Log)", returning = "returnValue")
    public void serviceAfterReturning(JoinPoint joinPoint, Object returnValue) throws Exception {
        Log log = getLog(joinPoint);

        String description = buildDescription(log, joinPoint, returnValue);
        createUserOperationLog(description);
    }

    private String buildDescription(Log log, JoinPoint joinPoint, Object returnValue) {
        String description = log.description();
        if (description.contains("${")) {
            description = buildDescFormArgs(description, joinPoint);
            description = buildDescFromReturnValue(description, returnValue, joinPoint);
        }
        return description;
    }

    private String buildDescFromReturnValue(String description, Object returnValue, JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class returnType = signature.getReturnType();
        String simpleName = returnType.getSimpleName();

        Pattern pattern = Pattern.compile(EXPRESSION_PREFIX + "(" + simpleName + ".*?)" + EXPRESSION_SUFFIX);
        Matcher matcher = pattern.matcher(description);
        while (matcher.find()) {
            String group = matcher.group(1);
            try {
                description = description.replace("${" + group + "}", getReflectionValue(returnValue, group));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return description;
    }

    private String buildDescFormArgs(String description, JoinPoint joinPoint) {
        Pattern pattern = Pattern.compile(EXPRESSION_FUll);
        Matcher matcher = pattern.matcher(description);

        Map<String, Object> parameters = buildParameterMap(joinPoint);
        while (matcher.find()) {
            String group = matcher.group(1);
            Object parameter = parameters.get(getParameterName(group));
            if (parameter != null) {
                try {
                    description = description.replace("${" + group + "}", getReflectionValue(parameter, group));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return description;
    }

    public String getParameterName(String group) {
        if (StringUtils.isEmpty(group)) {
            return null;
        }

        return group.split("\\.")[0];
    }

    private String getReflectionValue(Object target, String field) throws InvocationTargetException, IllegalAccessException {
        if (target == null) {
            return "";
        }

        if (!field.contains(".")) {
            return target.toString();
        }

        String[] split = field.split("\\.");

        for (int i = 1; i < split.length; i++) {
            String method = buildMethodName(split[i]);
            target = getFieldValue(target, method);
        }
        if (target == null) {
            return "";
        }
        return target.toString();
    }

    private Object getFieldValue(Object target, String methodName) throws InvocationTargetException, IllegalAccessException {
        Method method = ReflectionUtils.findMethod(target.getClass(), methodName);
        Object[] parameter = {};
        return method.invoke(target, parameter);
    }

    private String buildMethodName(String field) {
        return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
    }

    private Map<String, Object> buildParameterMap(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = signature.getParameterNames();

        Map<String, Object> parameters = new HashMap<String, Object>();
        for (int i = 0; i < parameterNames.length; i++) {
            String parameterName = parameterNames[i];
            parameters.put(parameterName, args[i]);
        }
        return parameters;
    }

    private Log getLog(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method.getAnnotation(Log.class);
    }

    private void createUserOperationLog(String description) {
        UserOperationLog operationLog;
        operationLog = new UserOperationLog(getUser(), new Date(), description);
        operationLog.save();
    }

    private User getUser() {
        UserAccount userAccount = (UserAccount) SecurityUtils.getSubject().getSession().getAttribute("user");
        if (userAccount == null) {
            return null;
        }
        return userAccount.getUser();
    }

}
